2024_darge_turkish_square.py

#

SPDX-FileCopyrightText: 2025 Carl Elebaut & Camille Frejafon SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

import bpy
import bmesh
import math
import random
from mathutils import Vector
import mathutils

bpy.ops.object.select_all(action="SELECT")  # Sélectionner tous les objets
bpy.ops.object.delete(use_global=False)  # Supprimer les objets
bpy.ops.outliner.orphans_purge(do_recursive=True)  # Nettoyer les données orphelines
#

Taille d’un carré

taille_carre = 5  # 5 unités Blender (correspondant à 20 cm)
#

Nom des colonnes et des lignes

colonnes = ["A", "B", "C", "D", "E"]
lignes = [1, 2, 3, 4, 5]
#

Fonction pour créer un carré

def creer_carre(position, taille, nom):
    bpy.ops.mesh.primitive_plane_add(size=taille, location=position)
    bpy.context.object.name = nom
#

Fonction pour ajouter du texte

def ajouter_texte(position, texte, taille):
    bpy.ops.object.text_add(location=position)
    objet_texte = bpy.context.object
    objet_texte.data.body = texte
    objet_texte.scale = (taille, taille, taille)
    objet_texte.name = f"Texte_{texte}"
#

Fonction pour générer un texte aléatoire

def texte_aleatoire():
    candidats = [1, 2, 3, 5]
    choix = random.sample(
        candidats, random.randint(1, len(candidats))
    )  # Choisir au moins un numéro au hasard
#

Appliquer la règle : 2 et 3 ne peuvent pas apparaître ensemble

    if 2 in choix and 3 in choix:
        choix.remove(random.choice([2, 3]))  # Retirer soit 2, soit 3
    return " ".join(map(str, sorted(choix)))
#
def texte_aleatoire2():
    candidats = [1, 6]
    choix = random.sample(candidats, random.randint(1, len(candidats)))
    return " ".join(map(str, sorted(choix)))
#

Code pour associer les chiffres (1, 2, 3, 4, 5, 6) à des formes (sinusoïde, cercles, etc.)

#
def sinusoide(location=(0, 0, 0)):
#

Code de la sinusoïde

    amplitude = 1  # Amplitude de la sinusoïde (5 cm)
    longueur = 5  # Longueur totale de la sinusoïde (10 cm)
    cycles = 3  # Nombre de cycles de la sinusoïde
    segments = 100  # Nombre de segments pour une courbe fluide
    points = []

    for i in range(segments + 1):
        x = (i / segments) * longueur - (longueur / 2)
        y = amplitude * math.sin((i / segments) * cycles * 2 * math.pi)
        z = 0
#

2D dans le plan XY

        points.append((x, y, z))

    mesh = bpy.data.meshes.new("SinusoidalMesh")
    edges = [(i, i + 1) for i in range(len(points) - 1)]
    mesh.from_pydata(points, edges, [])
    mesh.update()
    obj = bpy.data.objects.new("Sinusoidal", mesh)
    bpy.context.collection.objects.link(obj)
    obj.location = location
#
def cercles(location=(0, 0, 0)):
    nombre_cercles = 6
    diametre_cercle = 1
    rayon_cercle = diametre_cercle / 2
    espace_x = 5 / (nombre_cercles - 1)
    espace_y = 10 / (nombre_cercles - 1)

    for i in range(nombre_cercles):
        x = i * espace_x
        y = i * espace_y - 4
        bpy.ops.mesh.primitive_circle_add(
            radius=rayon_cercle,
            fill_type="NOTHING",
            location=(x + location[0], y + location[1], location[2]),
        )
#
def cercles_aleatoires(location=(0, 0, 0)):
#

Code des cercles aléatoires

    nombre_cercles = 10
    diametre_cercle = 1
    rayon_cercle = diametre_cercle / 2
    positions = []

    while len(positions) < nombre_cercles:
        x = random.uniform(-2.5, 2.5)
        y = random.uniform(-10, 10)
        z = 0

        overlap = False
        for pos in positions:
            distance = math.sqrt((x - pos[0]) ** 2 + (y - pos[1]) ** 2)
            if distance < diametre_cercle:
                overlap = True
                break
        if not overlap:
            positions.append((x, y))
            bpy.ops.mesh.primitive_circle_add(
                radius=rayon_cercle,
                fill_type="NOTHING",
                location=(x + location[0], y + location[1], location[2]),
            )
#

Récupérer l’objet créé

        cercle = bpy.context.object
#

Changer le nom de l’objet dans les calques

        cercle.name = "cercle_3"
#
def cercles_4(location=(0, 0, 0)):
#

Code des cercles de Bézier

    for _ in range(8):
        taille = random.uniform(0.5, 2)
        position_x = random.uniform(-2.5, 2.5)
        position_y = random.uniform(-10, 10)
        bpy.ops.curve.primitive_bezier_circle_add(
            radius=taille,
            location=(position_x + location[0], position_y + location[1], location[2]),
        )
#

Récupérer l’objet créé

        cercle = bpy.context.object
#

Changer le nom de l’objet dans les calques

        cercle.name = "cercle_4"
#
def points(location=(0, 0, 0)):
#

Code des points

    for _ in range(30):
        x = random.uniform(-2.2, 2.5)
        y = random.uniform(-10, 10)
        bpy.ops.mesh.primitive_uv_sphere_add(
            radius=0.05, location=(x + location[0], y + location[1], location[2])
        )
#

Récupérer l’objet créé

        cercle = bpy.context.object
#

Changer le nom de l’objet dans les calques

        cercle.name = "cercle_5"
#
def zigzag(location=(0, 0, 0)):
#

Code du zigzag

    amplitude = 1
    longueur = 8
    cycles = 1
    segments_per_cycle = 6
    points = []
    total_segments = cycles * segments_per_cycle

    for i in range(total_segments + 1):
        x = (i / total_segments) * longueur - (longueur / 2)
        y = amplitude if i % 2 == 0 else -amplitude
        z = 0
        points.append((x, y, z))

    mesh = bpy.data.meshes.new("AngularSinusoidalMesh")
    edges = [(i, i + 1) for i in range(len(points) - 1)]
    mesh.from_pydata(points, edges, [])
    mesh.update()
    obj = bpy.data.objects.new("zigzag2D", mesh)
    bpy.context.collection.objects.link(obj)
    obj.location = location
#

CHEMIN Fonction pour créer une courbe pour visualiser le chemin

def creer_chemin_courbe(chemin):
#

Créer une nouvelle courbe

    bpy.ops.curve.primitive_bezier_curve_add()
    courbe = bpy.context.object
    courbe.name = "Chemin"
    courbe.data.dimensions = "3D"
#

Supprimer les points par défaut

    courbe.data.splines.clear()
#

Ajouter une spline polyline pour le chemin

    spline = courbe.data.splines.new(type="POLY")
    spline.points.add(len(chemin) - 1)  # Ajouter le nombre de points nécessaires

    for i, (col, ligne) in enumerate(chemin):
        col_index = colonnes.index(col)
        ligne_index = lignes.index(ligne)
        x = col_index * taille_carre
        y = ligne_index * taille_carre
        z = 0.1  # Légèrement au-dessus des cases
        spline.points[i].co = (x, y, z, 1)  # (x, y, z, w)
#

Ajuster l’épaisseur de la courbe pour la rendre visible

    courbe.data.bevel_depth = 0.1  # Épaisseur
    courbe.data.bevel_resolution = 4  # Lissage de la courbe
#

Fonction pour générer un chemin respectant vos contraintes

def generer_chemin():
#

Choisir une case de départ dans la ligne 4

    col_depart = random.choice(colonnes)
    case_depart = (col_depart, 5)
#

Initialisation du chemin

    chemin = [case_depart]
    directions = [(1, 0), (-1, 0), (0, -1)]  # Droite, gauche, haut (pas de diagonale)
#

Générer le chemin

    while True:
        col, ligne = chemin[-1]
        col_index = colonnes.index(col)
        ligne_index = lignes.index(ligne)
#

Chercher les cases voisines valides

        voisins = []
        for dx, dy in directions:
            col_voisin_index = col_index + dx
            ligne_voisin_index = ligne_index + dy

            if 0 <= col_voisin_index < len(colonnes) and 0 <= ligne_voisin_index < len(
                lignes
            ):
                case_voisin = (colonnes[col_voisin_index], lignes[ligne_voisin_index])
                if (
                    case_voisin not in chemin
                ):  # Éviter de revenir sur une case déjà visitée
                    voisins.append(case_voisin)

        if not voisins:
            break  # Si aucun voisin valide, arrêter le chemin
#

Ajouter une case voisine au hasard au chemin

        case_suivante = random.choice(voisins)
        chemin.append(case_suivante)

    return chemin
#

Étape 2 : Création de la grille

for i, col in enumerate(colonnes):
    for j, ligne in enumerate(lignes):
#

Position de chaque carré

        x = i * taille_carre
        y = j * taille_carre
        z = 0  # Les carrés sont plats sur le so
#

Ajout d’un carré (mesh plane)

        bpy.ops.mesh.primitive_plane_add(size=taille_carre, location=(x, y, z))
        carre_nom = f"{col}{ligne}"
        bpy.context.object.name = carre_nom

        if ligne == 5:
            ajouter_texte((x, y, z + 0.1), "1", 1)

        if ligne == 1:
            if col == "D":
                ajouter_texte((x, y, z + 0.1), "4", 1)
        if ligne == 4:
            if col == "E":
                ajouter_texte((x, y, z + 0.1), "4", 1)
#

Ajouter du texte aléatoire dans les cases

        if col == "A" and ligne in [2, 3, 4]:
            texte = texte_aleatoire()
            ajouter_texte((x, y, z + 0.1), texte, 1)

        if col == "E" and ligne in [1, 2, 3]:
            texte = texte_aleatoire()
            ajouter_texte((x, y, z + 0.1), texte, 1)

        if col == "B" and ligne in [1, 3, 4]:
            texte = texte_aleatoire()
            ajouter_texte((x, y, z + 0.1), texte, 1)

        if col == "C" and ligne in [1, 2, 3, 4]:
            texte = texte_aleatoire()
            ajouter_texte((x, y, z + 0.1), texte, 1)

        if col == "D" and ligne in [2, 3, 4]:
            texte = texte_aleatoire()
            ajouter_texte((x, y, z + 0.1), texte, 1)

        if col == "B" and ligne in [2]:
            texte = texte_aleatoire2()
            ajouter_texte((x, y, z + 0.1), texte, 1)
        if col == "A" and ligne in [1]:
            texte = texte_aleatoire2()
            ajouter_texte((x, y, z + 0.1), texte, 1)
#

Étape 3 : Générer un chemin et le visualiser

chemin = generer_chemin()
creer_chemin_courbe(chemin)

offset_x = len(colonnes) * taille_carre + 2  # Déplacer à droite de la matrice existante
#

PARTITION

formes_par_chiffre = {
    1: sinusoide,
    2: cercles,
    3: cercles_aleatoires,
    4: cercles_4,
    5: points,
    6: zigzag,
}

hauteurs_matrice = {
    "A5": 0,
    "B5": 2,
    "C5": 3,
    "D5": 2,
    "E5": 0,
    "A4": 10,
    "B4": 10,
    "C4": 10,
    "D4": 10,
    "E4": 7,
    "A3": 10,
    "B3": 10,
    "C3": 10,
    "D3": 10,
    "E3": 7,
    "A2": 7,
    "B2": 7,
    "C2": 7,
    "D2": 7,
    "E2": 7,
    "A1": 7,
    "B1": 5,
    "C1": 0,
    "D1": 5,
    "E1": 7,
}
#

Parcourir le chemin

for i, (col, ligne) in enumerate(chemin):
    col_index = colonnes.index(col)
    ligne_index = lignes.index(ligne)
    x = col_index * taille_carre
    y = ligne_index * taille_carre
#

Identifier la clé de la matrice (exemple : “A5”, “B4”, …)

    case_id = f"{col}{ligne}"
#

Récupérer le texte (chiffres) de la case traversée

    texte_case = None
    for obj in bpy.context.scene.objects:
        if obj.type == "FONT" and (obj.location.x == x) and (obj.location.y == y):
            texte_case = obj.data.body  # Récupérer le texte brut
            break
#

Ajouter toutes les formes associées aux chiffres présents dans la case

    if texte_case:
        try:
            chiffres = list(
                map(int, texte_case.split())
            )  # Convertir le texte en une liste de chiffres
        except ValueError:
            chiffres = []  # Si une erreur de conversion se produit, ignorer cette case
#

Ajouter les formes correspondant aux chiffres

        for j, chiffre in enumerate(chiffres):
            if chiffre in formes_par_chiffre:
                forme = formes_par_chiffre[chiffre]
                hauteur = hauteurs_matrice.get(
                    case_id, 0
                )  # Récupérer la hauteur associée à la case
#

Définir une hauteur spécifique pour le chiffre 1

                if chiffre == 1 and case_id in hauteurs_matrice:
                    hauteur = hauteurs_matrice[case_id]
#

Ajouter la forme avec la hauteur définie (en 2D)

                forme(location=(offset_x + i * taille_carre + 10 + j * 2, hauteur, 0))
#

CUBE

dimension = 20
#

Position (x, y, z) en mètres

position = (150, 0, 10)  # Par exemple : 10 cm sur X, 20 cm sur Y, 30 cm sur Z
#

Ajouter un cube

bpy.ops.mesh.primitive_cube_add(size=dimension, location=position)
#

Optionnel : récupérer le cube créé pour d’autres modifications

cube = bpy.context.object
cube.name = "Cube"
#

Ajouter une courbe sinusoïdale 3D

def create_sinusoidal():
    min_x = 150 - 20 / 2
    max_x = 150 + 20 / 2
    min_y = 0 - 20 / 2
    max_y = 0 + 20 / 2
    min_z = 10 - 16 / 2
    max_z = 10 + 16 / 2

    center_x = 150
    center_y = 0
    center_z = 10
#

Ajouter une courbe de Bézier

    bpy.ops.curve.primitive_bezier_curve_add(location=(center_x, center_y, center_z))
    curve = bpy.context.object
    curve.name = "Sinusoidal 3D"
#

Récupérer les points de contrôle

    spline = curve.data.splines[0]
    spline.bezier_points.add(10)  # Ajouter des points pour la courbe
#

Placer le premier point de la sinusoïde sur la face inférieure du cube

    first_point = spline.bezier_points[0]
    first_point.co = (
        random.uniform(min_x, max_x) - center_x,
        random.uniform(min_y, max_y) - center_y,
        -10,
    )  # Position sur la face inférieure

    first_point.handle_left_type = "AUTO"
    first_point.handle_right_type = "AUTO"
#

Calculer les autres points de la courbe sinusoïdale

    for i, point in enumerate(
        spline.bezier_points[1:], start=1
    ):  # Commence après le premier point
#

Placer ces points de façon aléatoire dans le cube

        x = random.uniform(min_x, max_x) - center_x
        y = random.uniform(min_y, max_y) - center_y
        z = math.sin(i) * (max_z - min_z) / 2.5  # Application de la sinusoïde
        point.co = (x, y, z)

        point.handle_left_type = "AUTO"
        point.handle_right_type = "AUTO"
#

Convertir la courbe en tube

    curve.data.bevel_depth = 1  # Épaisseur du tube
    curve.data.bevel_resolution = 5  # Résolution du tube


create_sinusoidal()
#

Positionner les points générés sur la face supérieure du cube

def points_sur_face_superieure(cube):
#

Récupérer les dimensions et la position du cube

    dimensions = cube.dimensions
    position = cube.location
#

Calculer les bornes de la face supérieure et inférieure du cube

    xmin = position.x - dimensions.x / 2
    xmax = position.x + dimensions.x / 2
    ymin = position.y - dimensions.y / 2
    ymax = position.y + dimensions.y / 2
    zmax = position.z + dimensions.z / 2  # Haut du cube
    zmin = position.z - dimensions.z / 2  # Bas du cube
#

Créer une nouvelle collection pour les cercles

    mesh = bpy.data.meshes.new("GeneratedMesh")
    obj = bpy.data.objects.new("cercle_5_cube", mesh)
    bpy.context.scene.collection.objects.link(obj)
    bpy.context.view_layer.objects.active = obj
#

Créer un tableau pour placer les cercles

    bm = bmesh.new()

    for _ in range(60):  # Générer 50 cercles aléatoires
        x = random.uniform(-2.2, 2.7)  # Garder la logique originale
        y = random.uniform(-10, 10)
#

Remapper les coordonnées dans les bornes du cube

        x_mapped = xmin + (x + 2.2) / 5.5 * (xmax - xmin)
        y_mapped = ymin + (y + 10) / 20 * (ymax - ymin)
        z_mapped = zmax
#

Créer un cercle à l’emplacement calculé

        circle_verts = []
        num_segments = 16  # Nombre de segments pour le cercle
        radius = 0.2  # Rayon du cercle
        for i in range(num_segments):
            angle = 2 * math.pi * i / num_segments
            vert = bm.verts.new(
                (
                    x_mapped + radius * math.cos(angle),
                    y_mapped + radius * math.sin(angle),
                    z_mapped,
                )
            )
            circle_verts.append(vert)
#

Connecter les sommets en une face circulaire

        circle_face = bm.faces.new(circle_verts)
        circle_face.normal_update()
#

Extruder le cercle vers le bas

        extrude_result = bmesh.ops.extrude_face_region(bm, geom=[circle_face])
        extruded_geom = extrude_result["geom"]
        verts_extruded = [v for v in extruded_geom if isinstance(v, bmesh.types.BMVert)]
#

Calculer une extrusion aléatoire dans la limite du cube

        extrusion_height = random.uniform(
            1, zmax - 2
        )  # si je veux vers le bas remettre zmin
        bmesh.ops.translate(
            bm, verts=verts_extruded, vec=Vector((0, 0, -extrusion_height))
        )
#

Appliquer les modifications au maillage

    bm.to_mesh(mesh)
    mesh.update()
#

Appeler la fonction pour positionner et extruder les cercles

points_sur_face_superieure(cube)
#

Fonction pour créer un zigzag avec épaisseur en plan

def zigzag(location=(0, 0, 0), extrusion_length=5, largeur=0.5, epaisseur=0.5):
#

Paramètres du zigzag

    amplitude = 1
    longueur = 8
    cycles = 1
    segments_per_cycle = 6
    total_segments = cycles * segments_per_cycle
    points_top = []
    points_bottom = []
#

Génération des points du zigzag avec une épaisseur

    for i in range(total_segments + 1):
        y = (i / total_segments) * longueur - (longueur / 2)  # Zigzag sur Y
        z = amplitude if i % 2 == 0 else -amplitude  # Alternance sur Z
        x = 0  # Plan fixe pour le zigzag avant extrusion
#

Ajouter des points avec épaisseur

        points_top.append((x, y + epaisseur / 2, z))
        points_bottom.append((x, y - epaisseur / 2, z))
#

Relier les points pour former un maillage

    points = points_top + points_bottom
    faces = []
#

Créer des faces entre les points top et bottom

    for i in range(total_segments):
        faces.append((i, i + 1, i + 1 + total_segments + 1, i + total_segments + 1))
#

Création du maillage du zigzag

    mesh = bpy.data.meshes.new("ZigzagMesh")
    mesh.from_pydata(points, [], faces)
    mesh.update()
#

Ajouter le zigzag dans la scène

    obj = bpy.data.objects.new("Zigzag", mesh)
    bpy.context.collection.objects.link(obj)
    obj.location = location  # Appliquer la position spécifiée
#

Extrusion sur l’axe X

    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.mode_set(mode="EDIT")  # Passer en mode édition
    bpy.ops.mesh.extrude_region_move(
        TRANSFORM_OT_translate={"value": (extrusion_length, 0, 0)}
    )
    bpy.ops.object.mode_set(mode="OBJECT")  # Revenir en mode objet
#

Calcul des limites des faces du cube

cube_min_y = position[1] - (dimension / 2)
cube_max_y = position[1] + (dimension / 2)
cube_min_x = position[0] - (dimension / 2)
cube_max_x = position[0] + (dimension / 2)
cube_min_z = position[2] - (dimension / 2)
cube_max_z = position[2] + (dimension / 2)
#

Générer un ou deux zigzags de manière aléatoire

zigzag_count = random.randint(1, 2)  # Générer 1 ou 2 zigzags

for _ in range(zigzag_count):
#

Position aléatoire pour chaque zigzag

    random_y = random.uniform(cube_min_y + 5, cube_max_y - 5)  # Aléatoire sur Y
    random_x = cube_max_x  # Position fixe sur la face latérale (max X)
    random_z = random.uniform(cube_min_z, cube_max_z - 1)  # Aléatoire sur Z
#

Longueur d’extrusion aléatoire (minimum 2, maximum dans les limites du cube)

    max_extrusion_length = min(-dimension, 0)  # Limite max raisonnable pour extrusion
    extrusion_length = random.uniform(5, max_extrusion_length)
#

Appeler la fonction zigzag avec une position aléatoire et extrusion aléatoire

    zigzag(location=(random_x, random_y, random_z), extrusion_length=extrusion_length)
#

— Forme Cercles (face Y parallèle, extrusion perpendiculaire, corrigé) —

def cercles(location=(0, 0, 0), group_name="Cercles_2_cube"):
    nombre_cercles = 6
    diametre_cercle = 1
    rayon_cercle = diametre_cercle / 2
    espace_x = 5 / (nombre_cercles - 1)
    espace_z = 10 / (nombre_cercles - 1)

    heights = [
        2 + (13 / (nombre_cercles - 1)) * i for i in range(nombre_cercles)
    ]  # Hauteurs graduelles de 2 à 15
#

Créer une collection temporaire pour regrouper les cercles

    bpy.ops.object.select_all(action="DESELECT")  # Désélectionner tous les objets
    for i, height in enumerate(heights):
        x = i * espace_x - 2.5  # Décalage horizontal centré sur la face Y
        z = i * espace_z - 4  # Décalage vertical
        bpy.ops.mesh.primitive_circle_add(
            radius=rayon_cercle,
            fill_type="NOTHING",
            location=(
                location[0] + x,
                location[1] - 0.1,
                location[2] + z,
            ),  # Ajustement de la position Y
            rotation=(1.5708, 0, 0),  # Rotation pour aligner parallèlement à la face Y
        )
#

Extrusion du cercle perpendiculairement à la face Y

        bpy.ops.object.mode_set(mode="EDIT")  # Passer en mode édition
        bpy.ops.mesh.extrude_region_move(
            TRANSFORM_OT_translate={
                "value": (0, -height, 0)
            }  # Extruder vers l'intérieur (-Y)
        )
        bpy.ops.object.mode_set(mode="OBJECT")  # Revenir en mode objet
        bpy.context.object.name = (
            f"{group_name}_Circle_{i}"  # Nom unique pour chaque cercle
        )
#

Joindre tous les cercles dans un seul objet

    bpy.ops.object.select_all(action="DESELECT")  # Désélectionner tout
    for obj in bpy.context.scene.objects:
        if obj.name.startswith(group_name):
            obj.select_set(True)  # Sélectionner uniquement les cercles du groupe
    bpy.context.view_layer.objects.active = bpy.context.selected_objects[
        0
    ]  # Activer le premier objet sélectionné
    bpy.ops.object.join()  # Joindre tous les cercles en un seul objet
    bpy.context.object.name = group_name  # Renommer l'objet groupé
#

Générer entre 1 et 4 groupes de cercles aléatoires sur la face Y

cercles_count = random.randint(1, 4)  # Nombre de fois que les cercles apparaissent
for i in range(cercles_count):
    random_z = random.uniform(
        cube_min_z + 4, cube_max_z - 5
    )  # Position aléatoire sur Z
    random_y = (
        cube_max_y - 0.01
    )  # Position ajustée pour être légèrement à l'intérieur du cube
    random_x = random.uniform(
        cube_min_x + 2.5, cube_max_x - 2.5
    )  # Position aléatoire sur X
    cercles(location=(random_x, random_y, random_z), group_name=f"Cercles_2_cube{i}")
#

Fonction pour créer des cercles avec extrusion aléatoire

def cercles_aleatoires_extrudes(
    location=(0, 0, 0),
    nombre_cercles=30,
    diametre_cercle=1,
    min_extrude=2,
    max_extrude=18,
):
    rayon_cercle = diametre_cercle / 2
    positions = []
#

Création d’un nouveau maillage pour regrouper les cercles extrudés

    mesh = bpy.data.meshes.new("CerclesMesh")
    obj = bpy.data.objects.new("Cercles_3_cube", mesh)
    bpy.context.collection.objects.link(obj)
#

Liste des vertices, edges et faces pour construire le maillage

    vertices = []
    faces = []
#

Génération des cercles et extrusion

    for _ in range(nombre_cercles):
#

Position du cercle confinée à la face -X

        x = location[0]  # Les cercles sont sur la face -X
        y = random.uniform(-dimension / 2.1, dimension / 2.1)  # Confinement en Y
        z = random.uniform((-dimension / 2) + 11, dimension)  # Confinement en Z
#

Longueur d’extrusion aléatoire

        extrusion = random.uniform(min_extrude, max_extrude)
#

Génération des vertices pour un cercle

        start_index = len(vertices)
        num_segments = 32  # Pour un cercle "lisse"
        circle_vertices = []
        for i in range(num_segments):
            angle = 2 * math.pi * i / num_segments
            vx = x
            vy = y + math.cos(angle) * rayon_cercle
            vz = z + math.sin(angle) * rayon_cercle
            circle_vertices.append((vx, vy, vz))
#

Ajouter les vertices du cercle de base

        vertices.extend(circle_vertices)
#

Ajouter les vertices extrudés (parallèle à l’axe X)

        extruded_vertices = [(vx + extrusion, vy, vz) for vx, vy, vz in circle_vertices]
        vertices.extend(extruded_vertices)
#

Ajouter les faces entre les cercles de base et extrudés

        for i in range(num_segments):
            next_i = (i + 1) % num_segments
            base_1 = start_index + i
            base_2 = start_index + next_i
            extruded_1 = base_1 + num_segments
            extruded_2 = base_2 + num_segments
#

Face latérale

            faces.append([base_1, base_2, extruded_2, extruded_1])
#

Face du cercle de base (optionnelle)

        faces.append([start_index + i for i in range(num_segments)])
#

Face du cercle extrudé (optionnelle)

        faces.append([start_index + num_segments + i for i in range(num_segments)])
#

Construire le maillage regroupé

    mesh.from_pydata(vertices, [], faces)
    mesh.update()

    return obj
#

Ajouter des cercles extrudés aléatoires sur la face -X

cercles_aleatoires_extrudes(
    location=(position[0] - dimension / 2, position[1], position[2]),
    nombre_cercles=30,  # Ajuste selon le nombre souhaité
    diametre_cercle=1,  # Diamètre constant des cercles
    min_extrude=1,  # Longueur minimale de l'extrusion
    max_extrude=18,  # Longueur maximale de l'extrusion
)
#

Fonction pour ajouter des cercles sur la face -Y du cube

def cercles_4(location=(0, 0, 0)):
#

Définir les limites de la face -Y du cube

    face_y = location[1] - dimension / 2  # La position de la face -Y sur l'axe Y
    face_x_min = location[0] - dimension / 2  # Limite min de la face sur l'axe X
    face_x_max = location[0] + dimension / 2  # Limite max de la face sur l'axe X
    face_z_min = location[2] - dimension / 2  # Limite min de la face sur l'axe Z
    face_z_max = location[2] + dimension / 2  # Limite max de la face sur l'axe Z

    for index in range(20):  # nombre en tout
        taille = random.uniform(0.5, 1.5)  # Taille du cercle
        position_x = random.uniform(face_x_min + taille, face_x_max - taille)
        position_z = random.uniform(
            face_z_min + taille, face_z_max - taille
        )  # Placer sur l'axe Z
#

Ajouter un cercle de Bézier sur la face -Y

        bpy.ops.curve.primitive_bezier_circle_add(
            radius=taille, location=(position_x, face_y, position_z)
        )
#

Récupérer l’objet créé

        cercle = bpy.context.object
        cercle.rotation_euler = (
            1.5708,
            0,
            0,
        )  # Rotation de 90° (1.5708 rad) autour de l'axe X
#

Donner un nom spécifique à l’objet

        cercle.name = (
            f"cercle_4_cube{index + 1}"  # Exemple : cercle_4_1, cercle_4_2, ...
        )
#

Convertir le cercle de Bézier en mesh

        bpy.ops.object.convert(target="MESH")
#

Extrusion aléatoire entre 2 et 18 unités

        extrusion_height = random.uniform(2, 18)  # longueur
#

Calculer la direction perpendiculaire à la face -Y du cube (normale de la face)

        extrusion_direction = (
            0,
            1,
            0,
        )  # La direction perpendiculaire à la face -Y est sur l'axe Y
        extrusion_direction = mathutils.Vector(extrusion_direction)
#

Extruder l’objet selon cette direction

        bpy.ops.object.mode_set(mode="EDIT")  # Passer en mode édition
        bpy.ops.mesh.select_all(action="SELECT")  # Sélectionner tous les vertices
        bpy.ops.mesh.extrude_region_move(
            TRANSFORM_OT_translate={"value": extrusion_direction * extrusion_height}
        )  # Extruder
        bpy.ops.object.mode_set(mode="OBJECT")  # Revenir en mode objet
#

Appeler la fonction pour ajouter les cercles et les extruder en tubes

cercles_4(location=position)
#

Supprimer le cube tout en maintenant les autres objets

bpy.data.objects.remove(cube, do_unlink=True)